using System;
using System.Reflection;

using Pegasus.Diagnostics;
using Pegasus.Runtime.Serialization;
using Pegasus.Runtime.Serialization.Formatters.Xml;

namespace Pegasus.Workflow.Service
{
	/// <summary>
	/// The base class for custom message services 
	/// </summary>
	public abstract class WorkflowMessageService : IWorkflowService
	{
		// Local Instance Values
		private WorkflowService m_workflowService = null;

		/// <summary>
		/// The default message service and base class for custom message services 
		/// </summary>
		public WorkflowMessageService()
		{
		}

		/// <summary>
		/// Gets the workflow service that this service is attached to.
		/// </summary>
		/// <value>The workflow service.</value>
		public virtual WorkflowService WorkflowService
		{
			get
			{
				return m_workflowService;
			}
		}

		/// <summary>
		/// Starts this instance of the service.
		/// </summary>
		/// <param name="workflowService">The workflow service that owns this service.</param>
		public virtual void Start( WorkflowService workflowService )
		{
			m_workflowService = workflowService;
		}

		/// <summary>
		/// Stops this instance of the service.
		/// </summary>
		public virtual void Stop()
		{
			m_workflowService = null;
		}

		/// <summary>
		/// Sends the start message to start the workflow.
		/// </summary>
		/// <param name="workflowId">The workflow id.</param>
		public void SendWorkflowStartMessage( int workflowId )
		{
			if( !ProcessWorkflowStartMessage( workflowId ) )
			{
				ScheduleWorkflowStartMessage( workflowId );
			}
		}

		/// <summary>
		/// Sends the workflow restart message.
		/// </summary>
		/// <param name="workflowId">The workflow id.</param>
		/// <param name="startState">The start state.</param>
		public void SendWorkflowRestartMessage( int workflowId, string startState )
		{
			if( !ProcessWorkflowRestartMessage( workflowId, startState ) )
			{
				ScheduleWorkflowRestartMessage( workflowId, startState );
			}
		}

		/// <summary>
		/// Sends the data exchange message.
		/// </summary>
		/// <param name="dataExchangeServiceType">Type of the data exchange service.</param>
		/// <param name="eventInfo">The event info.</param>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="T:Pegasus.Workflow.Service2.WorkflowEventArgs"/> instance containing the event data.</param>
		public void SendDataExchangeMessage( Type dataExchangeServiceType, EventInfo eventInfo, object sender, WorkflowEventArgs args )
		{
			// Check Parameters
			ParamCode.AssertNotNull( dataExchangeServiceType, "dataExchangeServiceType" );
			ParamCode.AssertNotNull( eventInfo, "eventInfo" );
			// sender can be null
			ParamCode.AssertNotNull( args, "args" );

			DataExchangeMessage message = new DataExchangeMessage();
			message.WorkflowId = args.WorkflowId;
			message.DataExchangeServiceType = dataExchangeServiceType;
			message.EventInfo = eventInfo;
			message.Sender = null;
			message.Args = args;

			// Add the sender only if it's serializable.
			if( sender != null && sender.GetType().IsSerializable )
			{
				message.Sender = sender;
			}

			if( !ProcessDataExchangeMessage( message ) )
			{
				ScheduleDataExchangeMessage( message );
			}
		}

		/// <summary>
		/// Sends the workflow event message that was returned from the BuildWorkflowEventMessage() method.
		/// </summary>
		/// <param name="message">The message object.</param>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="T:Pegasus.Workflow.Service2.WorkflowEventArgs"/> instance containing the event data.</param>
		public void SendWorkflowEventMessage( WorkflowEventMessage message, object sender, WorkflowEventArgs args )
		{
			// Check Parameters
			ParamCode.AssertNotNull( message, "message" );
			// sender can be null
			ParamCode.AssertNotNull( args, "args" );

			if( !ProcessWorkflowEventMessage( message, sender, args ) )
			{
				ScheduleWorkflowEventMessage( message, sender, args );
			}
		}

		/// <summary>
		/// Registers the workflow event message.
		/// </summary>
		/// <param name="time">The time.</param>
		/// <param name="message">The message.</param>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="T:Pegasus.Workflow.Service2.WorkflowEventArgs"/> instance containing the event data.</param>
		/// <returns>The id of the message that can be used to unregister it later.  If -1 then the time has expired and the message was send.</returns>
		public int RegisterWorkflowEventMessage( DateTime time, WorkflowEventMessage message, object sender, WorkflowEventArgs args )
		{
			// Check Parameters
			// Any valid date time.
			ParamCode.AssertNotNull( message, "message" );
			// sender can be null
			// args can be null

			// If args

			// Has the time already passed
			if( time <= DateTime.Now )
			{
				SendWorkflowEventMessage( message, sender, args );
				return -1;
			}

			return ProcessRegisterWorkflowEventMessage( time, message, sender, args );
		}

		/// <summary>
		/// Unregisters the workflow event message.
		/// </summary>
		/// <param name="messageId">The message id that was obtained from the RegisterWorkflowEventMessage() method.</param>
		public void ReleaseWorkflowEventMessage( int messageId )
		{
			// Check Parameters
			ParamCode.AssertRange( messageId, 0, int.MaxValue, "messageId" );

			ProcessReleaseWorkflowEventMessage( messageId );
		}

		/// <summary>
		/// Builds the workflow event message.
		/// </summary>
		/// <param name="workflowEventHandler">The workflow event handler.</param>
		/// <returns></returns>
		public WorkflowEventMessage BuildWorkflowEventMessage( EventHandler<WorkflowEventArgs> workflowEventHandler )
		{
			// Check Parameters
			ParamCode.AssertNotNull( workflowEventHandler, "workflowEventHandler" );

			WorkflowEventMessage message = new WorkflowEventMessage();

			Type eventTargetType = workflowEventHandler.Target.GetType();

			// The event method must be on an instance of a workflow object or a state object
			if( eventTargetType.IsSubclassOf( typeof( Workflow ) ) )
			{
				Workflow workflow = (Workflow) workflowEventHandler.Target;
				message.WorkflowId = workflow.WorkflowId;
				message.StateName = null;
				message.MethodName = workflowEventHandler.Method.Name;
			}
			else if( eventTargetType.IsSubclassOf( typeof( WorkflowState ) ) )
			{
				WorkflowState state = (WorkflowState) workflowEventHandler.Target;
				message.WorkflowId = state.Workflow.WorkflowId;
				message.StateName = state.Name;
				message.MethodName = workflowEventHandler.Method.Name;
			}
			else
			{
				throw new WorkflowException( "Can not register a non-workflow event handler.  The handler must be on an valid instance of a workflow object or workflow state object." );
			}

			return message;
		}

		/// <summary>
		/// Processes the workflow start message.
		/// </summary>
		/// <param name="workflowId">The workflow id.</param>
		/// <returns>True if the message was process, False if the message should be recycled and processed later.</returns>
		protected abstract bool ProcessWorkflowStartMessage( int workflowId );

		/// <summary>
		/// Processes the workflow start message.
		/// </summary>
		/// <param name="workflowId">The workflow id.</param>
		/// <param name="startState">The start state.</param>
		/// <returns>True if the message was process, False if the message should be recycled and processed later.</returns>
		protected abstract bool ProcessWorkflowRestartMessage( int workflowId, string startState );

		/// <summary>
		/// Processes the data exchange message.
		/// </summary>
		/// <param name="message">The message.</param>
		/// <returns>True if the message was process, False if the message should be recycled and processed later.</returns>
		protected abstract bool ProcessDataExchangeMessage( DataExchangeMessage message );

		/// <summary>
		/// Processes the workflow event message.
		/// </summary>
		/// <param name="message">The message.</param>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="T:Pegasus.Workflow.Service2.WorkflowEventArgs"/> instance containing the event data.</param>
		/// <returns>True if the message was process, False if the message should be recycled and processed later.</returns>
		protected abstract bool ProcessWorkflowEventMessage( WorkflowEventMessage message, object sender, WorkflowEventArgs args );

		/// <summary>
		/// Processes the register workflow event message.
		/// </summary>
		/// <param name="time">The time.</param>
		/// <param name="message">The message.</param>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="T:Pegasus.Workflow.Service2.WorkflowEventArgs"/> instance containing the event data.</param>
		/// <returns></returns>
		protected abstract int ProcessRegisterWorkflowEventMessage( DateTime time, WorkflowEventMessage message, object sender, WorkflowEventArgs args );

		/// <summary>
		/// Processes the unregister workflow event message.
		/// </summary>
		/// <param name="messageId">The message id.</param>
		protected abstract void ProcessReleaseWorkflowEventMessage( int messageId );

		/// <summary>
		/// Schedules the workflow start message.
		/// </summary>
		/// <param name="workflowId">The workflow id.</param>
		protected virtual void ScheduleWorkflowStartMessage( int workflowId )
		{
		}

		/// <summary>
		/// Schedules the workflow restart message.
		/// </summary>
		/// <param name="workflowId">The workflow id.</param>
		/// <param name="startState">The start state.</param>
		protected virtual void ScheduleWorkflowRestartMessage( int workflowId, string startState )
		{
		}

		/// <summary>
		/// Schedules the data exchange message.
		/// </summary>
		/// <param name="message">The message.</param>
		protected virtual void ScheduleDataExchangeMessage( DataExchangeMessage message )
		{
		}

		/// <summary>
		/// Schedlues the workflow event message.
		/// </summary>
		/// <param name="message">The message.</param>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="T:Pegasus.Workflow.Service.WorkflowEventArgs"/> instance containing the event data.</param>
		protected virtual void ScheduleWorkflowEventMessage( WorkflowEventMessage message, object sender, WorkflowEventArgs args )
		{
		}

		/// <summary>
		/// Dispatches the start message.
		/// </summary>
		/// <param name="workflowId">The workflow id.</param>
		protected void DispatchWorkflowStartMessage( int workflowId )
		{
			// Check Parameters
			ParamCode.AssertRange( workflowId, 1, int.MaxValue, "workflowId" );

			using( WorkflowContext context = m_workflowService.GetWorkflow( workflowId, false ) )
			{
				Workflow workflow = context.Workflow;
				workflow.PushStatus( WorkflowStatus.Running );
				try
				{
					workflow.SetState( workflow.InitialState );
				}
				catch( Exception e )
				{
					context.Workflow.FireUnhandledException( e );
				}
				finally
				{
					workflow.PopStatus();
				}
			}
		}

		/// <summary>
		/// Dispatches the workflow restart message.
		/// </summary>
		/// <param name="workflowId">The workflow id.</param>
		/// <param name="startState">The start state.</param>
		protected void DispatchWorkflowRestartMessage( int workflowId, string startState )
		{
			// Check Parameters
			ParamCode.AssertRange( workflowId, 1, int.MaxValue, "workflowId" );
			ParamCode.AssertNotEmpty( startState, "startState" );

			using( WorkflowContext context = m_workflowService.GetWorkflow( workflowId, false ) )
			{
				Workflow workflow = context.Workflow;
				workflow.PushStatus( WorkflowStatus.Running );
				try
				{
					context.GetService<WorkflowTrackingService>().RecordRestartedMessage( context, startState );
					workflow.SetState( startState );
					workflow.FireRestarted();
				}
				catch( Exception e )
				{
					context.Workflow.FireUnhandledException( e );
				}
				finally
				{
					workflow.PopStatus();
				}
			}
		}

		/// <summary>
		/// Dispatches the data exchange message.
		/// </summary>
		/// <param name="message">The message.</param>
		protected void DispatchDataExchangeMessage( DataExchangeMessage message )
		{
			// Check Parameters
			ParamCode.AssertNotNull( message, "message" );

			using( WorkflowContext context = m_workflowService.GetWorkflow( message.WorkflowId, false ) )
			{
				Workflow workflow = context.Workflow;
				workflow.PushStatus( WorkflowStatus.Running );
				try
				{
					// Now get the data exchange for the context
					Type dataExchangeType = message.DataExchangeServiceType;
					WorkflowDataExchangeService dataExchange = (WorkflowDataExchangeService) context.GetService( dataExchangeType );
					if( dataExchange != null )
					{
						// Get the delegate field from the object type
						FieldInfo delegateField = dataExchangeType.GetField( message.EventInfo.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
						Delegate delegateValue = (Delegate) delegateField.GetValue( dataExchange );

						// If the delegate value is null then no one has registered for the event.
						if( delegateValue != null )
						{
							context.GetService<WorkflowTrackingService>().RecordDataExchangeMessage( context, message );
							
							// If the deliver to current state only is on then walk the delegates that are
							// attached to the event and deliver it to only the current state classes or
							// to the workflow class objects.
							if( message.Args.DeliverToCurrentStateOnly )
							{
								string currentState = context.Workflow.CurrentState;
								foreach( Delegate subDelegate in delegateValue.GetInvocationList() )
								{
									// If the delegate is a workflow class OR if the delegate is a workflow 
									// state class and it is the current state then fire the event.
									if( subDelegate.Target is Workflow ||
										( subDelegate.Target is WorkflowState && ( (WorkflowState) subDelegate.Target ).Name == currentState ) )
									{
										subDelegate.DynamicInvoke( new object[] { message.Sender, message.Args } );
									}
								}
							}
							else
							{
								// Deliver the event to everyone that is attached to the event.
								delegateValue.DynamicInvoke( new object[] { message.Sender, message.Args } );
							}
						}
					}
				}
				catch( Exception e )
				{
					context.Workflow.FireUnhandledException( e );
				}
				finally
				{
					workflow.PopStatus();
				}
			}
		}

		/// <summary>
		/// Dispatches the workflow event message.
		/// </summary>
		/// <param name="message">The message.</param>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="T:Pegasus.Workflow.Service2.WorkflowEventArgs"/> instance containing the event data.</param>
		protected void DispatchWorkflowEventMessage( WorkflowEventMessage message, object sender, WorkflowEventArgs args )
		{
			// Check Parameters
			ParamCode.AssertNotNull( message, "message" );
			// Sender can be null
			ParamCode.AssertNotNull( args, "args" );

			// If there is no registed sender object then send the message service 
			// as the sender of the event.
			if( sender == null )
			{
				sender = this;
			}

			// Get the target workflow
			using( WorkflowContext context = m_workflowService.GetWorkflow( message.WorkflowId, false ) )
			{
				Workflow workflow = context.Workflow;
				workflow.PushStatus( WorkflowStatus.Running );
				try
				{
					context.GetService<WorkflowTrackingService>().RecordWorkflowEventMessage( context, message );

					// Is this a state or workflow method
					if( string.IsNullOrEmpty( message.StateName ) )
					{
						// Workflow method
						CallWorkflowEventMethod( context.Workflow, message.MethodName, sender, args );
					}
					else
					{
						// State method
						WorkflowState state = context.Workflow.States[ message.StateName ];

						// If we are delivering the event only to the current state then make
						// sure that this state is the current state.
						if( !args.DeliverToCurrentStateOnly || 
							( args.DeliverToCurrentStateOnly && state.Name == context.Workflow.CurrentState ) )
						{
							CallWorkflowEventMethod( state, message.MethodName, sender, args );
						}
						
					}
				}
				catch( Exception e )
				{
					context.Workflow.FireUnhandledException( e );
				}
				finally
				{
					workflow.PopStatus();
				}
			}
		}

		/// <summary>
		/// Calls the workflow event method.
		/// </summary>
		/// <param name="objectToCall">The object to call.</param>
		/// <param name="methodName">Name of the method.</param>
		/// <param name="sender">The sender.</param>
		/// <param name="args">The <see cref="T:Pegasus.Workflow.Service2.WorkflowEventArgs"/> instance containing the event data.</param>
		private void CallWorkflowEventMethod( object objectToCall, string methodName, object sender, WorkflowEventArgs args )
		{
			// Check Parameters
			ParamCode.AssertNotNull( objectToCall, "objectToCall" );
			ParamCode.AssertNotEmpty( methodName, "methodName" );
			// Sender can be null
			ParamCode.AssertNotNull( args, "args" );

			Type objectType = objectToCall.GetType();
			MethodInfo methodInfo = objectType.GetMethod( methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
			while( methodInfo == null && objectType != typeof( object ) )
			{
				objectType = objectType.BaseType;
				methodInfo = objectType.GetMethod( methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic );
			}

			if( methodInfo != null )
			{
				methodInfo.Invoke( objectToCall, new object[] { sender, args } );
			}
			else
			{
				throw new WorkflowException( "Unable to find method {0} on type {1} to deliver workflow event", methodName, objectType );
			}
		}

		/// <summary>
		/// This class/struct is used the pass information to and from the message service
		/// </summary>
		[Serializable]
		public class DataExchangeMessage
		{
			/// <summary>
			/// 
			/// </summary>
			public int WorkflowId;
			
			/// <summary>
			/// 
			/// </summary>
			public Type DataExchangeServiceType;
			
			/// <summary>
			/// 
			/// </summary>
			public EventInfo EventInfo;
			
			/// <summary>
			/// 
			/// </summary>
			public object Sender;
			
			/// <summary>
			/// 
			/// </summary>
			public WorkflowEventArgs Args;
		}

		/// <summary>
		/// This class/struct is used to pass information about an workflow event
		/// </summary>
		[Serializable]
		public class WorkflowEventMessage
		{
			/// <summary>
			/// 
			/// </summary>
			public int WorkflowId;
			
			/// <summary>
			/// 
			/// </summary>
			public string StateName;
			
			/// <summary>
			/// 
			/// </summary>
			public string MethodName;
		}
	}
}
